/**
 * 
 */
package ipc;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.TreeMap;

import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import dd.discrete.ADD;
import dd.discrete.ADDDNode;
import dd.discrete.ADDNode;
import dd.discrete.DD;

import util.Pair;
/**
 * @author NR
 *
 * TODO
 */
public class DifferenceDisplayer implements Runnable{


	ArrayBlockingQueue<Pair<ADD, Pair<Integer, HashMap<Long, TreeMap<String, Boolean>>>>>	_bq1;
	ArrayBlockingQueue<Pair<ADD, Pair<Integer, HashMap<Long, TreeMap<String, Boolean>>>>> _bq2;
	private HashMap<TreeMap<String, Boolean> , Integer>	_hmTm2ID = new HashMap<TreeMap<String, Boolean>, Integer>();
	private int _actid = 0;
	/**
	 * @param iter1 
	 * @param iter2 
	 * @param pBq1 
	 * @param pBq2 
	 * @param bq 
	 * @param lock1 
	 * @param lock2 
	 * 
	 */
	public DifferenceDisplayer(ArrayBlockingQueue<Pair<ADD, Pair<Integer, HashMap<Long, TreeMap<String, Boolean>>>>> pBq1, 
			ArrayBlockingQueue<Pair<ADD, Pair<Integer, HashMap<Long, TreeMap<String, Boolean>>>>> pBq2  ) {
		_bq1 = pBq1;
		_bq2 = pBq2;
	}
	
//	@Override
	public void run() {
		
		try {
			while( true ) {
				
//				Thread.sleep(10000);
				
//				System.out.println(_iter1.isStuffAvailable() + " " + _iter2.isStuffAvailable() );
//				System.out.println(_bq1.size() + " " + _bq2.size() );
				
				if( _bq1.size() == 1 && _bq2.size() == 1 ) {
					Pair<ADD, Pair<Integer, HashMap<Long, TreeMap<String, Boolean>>>> ret1 = _bq1.peek();
					Pair<ADD, Pair<Integer, HashMap<Long, TreeMap<String, Boolean>>>> ret2 = _bq2.peek();
					System.out.println("displaying");
					showDifference(ret1._o1, ret1._o2._o1, ret1._o2._o2, ret2._o1, ret2._o2._o1, ret2._o2._o2);
//					System.in.read();
					_bq1.remove();
					_bq2.remove();
				}
				
					
				
			}
//				System.out.println(_bq.size());
				
//				System.out.println("*" + _bq1.size());
//				System.out.println("*" + _bq2.size());

		}catch( Exception e ) {
			e.printStackTrace();
		}
		
	}
	
	//does not assume both context have same order
	private void showDifference(ADD context1, int dd1, HashMap<Long, TreeMap<String, Boolean>> leafmap1, ADD context2, int dd2, HashMap<Long, TreeMap<String, Boolean>> leafmap2) {
		
//		context1.getGraph(dd1).launchViewer();
//		context2.getGraph(dd2).launchViewer();
//		try
//		{
//			System.in.read();
//		} catch (IOException e)
//		{
			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
		//make new context
		ArrayList ord = new ArrayList<String>();
		
		Set gids1 = context1.getGIDs(dd1);
		Set gids2 = context2.getGIDs(dd2);
		
		for( Object o : context1._alOrder ) {
			Integer id = (Integer)o;
			String varname = (String) (context1._hmID2VarName.get(id));
			ord.add(varname);
			
			boolean x = gids1.contains((Integer)( context1._hmVarName2ID.get(varname) ) );
			boolean y = gids2.contains((Integer)( context2._hmVarName2ID.get(varname) ) ) ;

			if( ( x && !y ) || ( !x && y) ) {
				System.err.println("one has " + varname + " while other does not.");
				System.exit(1);
			}
			
		}
		
		ADD context = new ADD(ord);
		
		//transfer diagram to new context
		//align in the process
		int dd1t = transfer(context, context1, dd1, 0, context1.getGIDs(dd1), leafmap1);
		int dd2t = transfer(context, context2, dd2, 0, context2.getGIDs(dd2), leafmap2);
		
		
		//sanity
//		HashMap eval = new HashMap();
//		eval.put("elevator_at_floor__e0_f0", true);
//		eval.put("elevator_at_floor__e0_f1", false);
//		eval.put("elevator_at_floor__e0_f2", false);
//		eval.put("elevator_closed__e0", true);
//		eval.put("elevator_dir_up__e0", true);	
//		eval.put("person_in_elevator_going_down__e0", false);
//		eval.put("person_in_elevator_going_up__e0", false);
//		eval.put("person_waiting_down__f2", false);
//		eval.put("person_waiting_down__f0", false);
//		eval.put("person_waiting_down__f1", false);
//		eval.put("person_waiting_up__f2", false);
//		eval.put("person_waiting_up__f1", false);
//		eval.put("person_waiting_up__f0", false);
//		
//		double val1 = context1.evaluate(dd1, eval);
//		double val2 = context2.evaluate(dd2, eval);
//		double val1t = context.evaluate(dd1t, eval);
//		double val2t = context.evaluate(dd2t, eval);
//		
//		if( val1t != val2t ) {
//			System.err.println("faulty transfer");
//			System.exit(1);
//		}
		//show dds
//		context.getGraph(dd1t).launchViewer();
//		context.getGraph(dd2t).launchViewer();
		
		int diff = context.applyInt(dd1t, dd2t, DD.ARITH_MINUS);
//		diff = context.applyInt(diff, dd1t, DD.ARITH_DIV );
		
		HashSet leaves = new HashSet(); 
		context.collectLeaves(diff, leaves);
		
		System.out.println(leaves);
		
		double minError = 0.0d;
		double maxError = 0.0d;
		long size = 0;
		
		double max = Double.NEGATIVE_INFINITY;
		double min = Double.POSITIVE_INFINITY;
		
		for( Object l : leaves ) {
			ADDDNode node = (ADDDNode)l;
			if( node._dLower == ADD.NEGINF ) {
				
				if( node._dUpper != ADD.NEGINF ) {
					System.out.println("Uh oh!");
				}
				continue;
			}
			minError += node._dLower;
			maxError += node._dUpper;
			++size;
			
			if( node._dLower > max ) {
				max = node._dLower;
			}
			
			if( node._dLower < min ) {
				min = node._dLower;
			}
			
		}
		
		System.out.println(minError);
		System.out.println(maxError);
		System.out.println(minError/size);
		System.out.println(maxError/size);
		
		System.out.println(max);
		System.out.println(min);
//		System.out.println(" average : " + sum );
//		System.out.println(" Max : " + Collections.(leaves))
		
//		context.getGraph(dd1t).launchViewer();
//		context.getGraph(dd2t).launchViewer();
//		
//		context.getGraph(diff).launchViewer();
	}
	
	private int transfer(ADD context, ADD contexti, int ddi, int top, Set gids, HashMap<Long, TreeMap<String, Boolean>> leafmap) {
		
		if( top == context._alOrder.size()) {
			ADDNode node = contexti.getNode(ddi);
			if( node instanceof ADDDNode ) {
				ADDDNode noder = ((ADDDNode)node);
				if( leafmap != null ) {
					TreeMap<String, Boolean> assigns = leafmap.get((long)(noder._dUpper));
					
					if( assigns == null ) {
						System.err.println("could not lookup leaf node in map");
						System.exit(1);
					}
					
					if( _hmTm2ID.get(assigns) == null ) {
						System.out.println("first time " + assigns);
						_hmTm2ID.put(assigns, ++_actid);
					}
					
					int newid = _hmTm2ID.get(assigns);
					
//					System.out.println(noder._dUpper + "->" + assigns + "->" + newid);
						
					return context.getConstantNode((double)newid);
				}
				return context.getLeafNode(noder._dLower, noder._dUpper, noder._sLowerLabel, noder._sUpperLabel);
			}else {
				System.err.println("leaf not found?!?");
				System.exit(1);
			}
		}
		
		int contextId = (Integer)(context._alOrder.get(top));
		
		int contextiId = (Integer)contexti._hmVarName2ID.get(
				context._hmID2VarName.get(contextId));
		
		if( !gids.contains(contextiId) ) {
			return transfer(context, contexti, ddi, top+1, gids, leafmap);
		}else {
			int highdd = contexti.restrict(ddi, contextiId, DD.RESTRICT_HIGH);
			highdd = transfer(context, contexti, highdd, top+1, contexti.getGIDs(highdd), leafmap);
			
			int lowdd = contexti.restrict(ddi, contextiId, DD.RESTRICT_LOW);
			lowdd = transfer(context, contexti, lowdd, top+1, contexti.getGIDs(lowdd), leafmap);
			
			int ret = context.applyInt(
					context.applyInt(highdd, context.getVarNode(contextId, 0.0d, 1.0d), DD.ARITH_PROD),
					context.applyInt(lowdd, context.getVarNode(contextId, 1.0d, 0.0d), DD.ARITH_PROD),
				DD.ARITH_SUM);
			return ret;
		}
		
	}
	
	public static void main(String[] args) throws Exception {
		
		ArrayBlockingQueue<Pair<ADD, 
		Pair<Integer, HashMap<Long, TreeMap<String, Boolean>>>>> bq1 = 
			new ArrayBlockingQueue<Pair<ADD, 
				Pair<Integer, HashMap<Long, TreeMap<String, Boolean>>>>>(1);
		
		ArrayBlockingQueue<Pair<ADD, 
			Pair<Integer, HashMap<Long, TreeMap<String, Boolean>>>>> bq2 = 
				new ArrayBlockingQueue<Pair<ADD, 
					Pair<Integer, HashMap<Long, TreeMap<String, Boolean>>>>>(1);
		
		ValueIterationConcurrent vic = new ValueIterationConcurrent(args[0], 
				args[1], 0.1, 
				ValueIterationConcurrent.ORDER_GUESS, -1, "./", bq1);
		
//		MaxDecomposition maxd = new MaxDecomposition(args[0], 
//				args[1], 0.1, 
//				MaxDecomposition.ORDER_GUESS, -1, "./", bq2);
		
		ValueIteration vi = new ValueIteration(args[0], args[1], Double.parseDouble(args[2]), ValueIteration.ORDER_GUESS,
				-1, "./", bq2);
		
//		StructuredPolicyIteration spi = new StructuredPolicyIteration(args[0], args[1], Double.parseDouble(args[2]), 
//				StructuredPolicyIteration.ORDER_XAXP, -1, "./", bq2, Integer.parseInt(args[3]));
		
		Thread vicWorker = new Thread(vic, "Concurrent VI");
			
//		Thread maxWorker = new Thread(maxd, "max decomposition");

		Thread viWorker = new Thread(vi, "VI");
		
//		Thread spiWorker = new Thread(spi, "SPI");
		
		DifferenceDisplayer differ = new DifferenceDisplayer(bq1,bq2);
		Thread differWorker = new Thread(differ, "differ");
		
		differWorker.start();
		
//		spiWorker.start();
//		maxWorker.start();
		viWorker.start();
		vicWorker.start();
		
	}
}
